import React, { useEffect, useMemo, useRef, useState } from 'react';
import { useEventListener } from 'ahooks';
import { createPortal } from 'react-dom';

export type Range = [number, number];

export interface MousePosition {
  x: number;
  y: number;
}

export interface ResizeInfo {
  /**
   * resize 鼠标移动时偏移量
   */
  movement: MousePosition;

  /**
   * resize 的目标元素
   */
  target: HTMLElement;

  /**
   * target 的开始调整时的 DOMRect 数据
   */
  targetStartRect: DOMRect;
}

export interface ResizableProps {
  children: React.ReactNode;

  /**
   * 用于拖动的手柄，handle 必须是一个React元素
   */
  renderHandle: (resizing: boolean) => React.ReactElement<React.HTMLProps<HTMLElement>>;

  /**
   * 调整限制范围：[ x: [minX, maxX], y: [minY, maxY] ]，用于控制调整的范围
   */
  resizeRange?: [Range, Range];

  /**
   * Resize Event
   * @param movement - 移动的距离
   * @returns void
   */
  onResize?: (info: ResizeInfo) => void;
}

/**
 * Resizable 组件用于实现拖拽可调整栏目宽高
 * @param children - 需要调整大小的元素，注意：必须是一个 React 元素，且必须能够接受 ref 属性
 * @param handle - 用于拖动的手柄，必须是一个 React 元素
 * @param resizeRange - 调整限制范围：[ x: [minX, maxX], y: [minY, maxY] ]，用于控制调整的范围
 * @param onResize - Resize 事件回调函数，接收一个包含 movement、target 和 targetStartRect 的对象
 */
function ResizableNoMemoized(props: ResizableProps) {
  const { children, renderHandle, resizeRange, onResize } = props;

  const [resizing, setResizing] = useState(false);
  const [handleTarget, setHandleTarget] = useState<HTMLElement>();

  const target = useRef<HTMLElement>(null);
  const targetStartRect = useRef<DOMRect | null>(null);
  const mouseStart = useRef<MousePosition | null>(null);
  const movement = useRef<MousePosition>({ x: 0, y: 0 });
  const handleClientRange = useRef<[Range, Range]>(undefined); // handle 的 clientX, clientY 范围

  useEffect(() => {
    if (target.current) {
      setHandleTarget(target.current);
    }
  }, []);

  useEventListener('mouseup', () => {
    mouseStart.current = null;
    targetStartRect.current = null;
    setResizing(false);
  });

  useEventListener('mousemove', (e) => {
    if (!mouseStart.current) {
      return;
    }

    let clientX = e.clientX;
    let clientY = e.clientY;

    if (handleClientRange.current) {
      const [[minX, maxX], [minY, maxY]] = handleClientRange.current;
      clientX = Math.min(Math.max(clientX, minX), maxX);
      clientY = Math.min(Math.max(clientY, minY), maxY);
    }

    const offsetX = clientX - mouseStart.current.x;
    const offsetY = clientY - mouseStart.current.y;

    movement.current = {
      x: offsetX,
      y: offsetY,
    };

    onResize?.({
      movement: movement.current,
      target: target.current!,
      targetStartRect: targetStartRect.current!,
    });
  });

  const handle = useMemo(() => renderHandle(resizing), [renderHandle, resizing]);

  if (!React.isValidElement(handle)) {
    // eslint-disable-next-line no-console
    console.warn('Resizable: handle is not a valid React element, skipping...');
    return children;
  }

  let child = children;

  if (React.Children.count(children) > 1) {
    // eslint-disable-next-line no-console
    console.warn(
      'Resizable: children must be a single React element, otherwise the handle will take the first element as the target element.'
    );
    child = React.Children.toArray(children)[0];
  }

  if (React.isValidElement<React.HTMLProps<HTMLElement>>(child)) {
    const realHandle = React.cloneElement<React.HTMLProps<HTMLElement>>(handle, {
      key: 'handle',
      onMouseDown(e) {
        // 开启移动，此后鼠标可能会移出handle区域，所以监听 document 的 mousemove,mouseup 事件
        setResizing(true);
        // 开始移动时鼠标位置
        mouseStart.current = { x: e.clientX, y: e.clientY };
        // 开始移动时 target 的 DOMRect 数据
        const domRect = target.current?.getBoundingClientRect();
        targetStartRect.current = domRect || null;
        // 初始时：计算 handle 的 clientX, clientY 范围
        if (resizeRange && !handleClientRange.current) {
          const [[minX, maxX], [minY, maxY]] = resizeRange;
          const { clientX, clientY } = e;
          handleClientRange.current = [
            [minX + clientX, maxX + clientX],
            [minY + clientY, maxY + clientY],
          ];
        }
      },
    });
    const childElement = React.cloneElement(child, {
      ref: target,
      style: resizing
        ? {
            ...child.props.style,
            transition: 'none',
            userSelect: 'none',
          }
        : child.props.style,
    });
    return (
      <>
        {childElement}
        {handleTarget && createPortal(realHandle, handleTarget)}
      </>
    );
  }

  // eslint-disable-next-line no-console
  console.warn('Resizable: children is not includes a valid React element, skipping...');
  return children;
}

export const Resizable = React.memo(ResizableNoMemoized);
